package ruleengine.worker;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.google.common.graph.ImmutableNetwork;
import com.google.common.graph.MutableNetwork;
import com.google.common.graph.NetworkBuilder;
import com.ruleengine.util.model.Message;
import com.ruleengine.util.model.Metadata;
import com.ruleengine.worker.model.Chain;
import com.ruleengine.worker.model.Relation;
import com.ruleengine.worker.model.Rule;

import ruleengine.BaseTest;

/**
 * @author lianghanwei on 2024/3/3
 */
public class ChainBaseTest extends BaseTest {

    /**
     * Chain
     */
    protected Chain buildChain(List<Rule> rules, List<Relation> relations, Chain.State state,
        ImmutableNetwork<Rule, Relation> topology) {
        return buildChain(rules, relations, state, topology, Chain.RunnerType.DISRUPTOR);
    }

    protected Chain buildChain(List<Rule> rules, List<Relation> relations, Chain.State state,
        ImmutableNetwork<Rule, Relation> topology, Chain.RunnerType runnerType) {
        Chain.Spec spec = Chain.Spec.builder()
            .state(state)
            .rules(rules)
            .relations(relations)
            .topology(topology)
            .build();

        return Chain.builder()
            .metadata(buildMetadata("Chain", "1"))
            .spec(spec)
            .build();
    }

    protected Chain buildChain(int ruleCount) {
        List<Rule> rules = IntStream.range(0, ruleCount)
            .mapToObj(e -> buildSimpleLogRule("rule-" + e))
            .collect(Collectors.toList());

        MutableNetwork<Rule, Relation> topology = NetworkBuilder.directed().build();

        List<Relation> relations = new LinkedList<>();
        for (int i = 0; i < rules.size() - 1; i++) {
            Relation relation = buildRelation(rules.get(i), rules.get(i + 1));
            relations.add(relation);

            topology.addEdge(rules.get(i), rules.get(i + 1), relation);
        }

        return buildChain(rules, relations, Chain.State.ENABLED, ImmutableNetwork.copyOf(topology));
    }

    /**
     * Rule
     */
    protected Rule buildRule(String name, Rule.RuleType type, Map<String, String> args) {
        Rule.Spec spec = Rule.Spec.builder()
            .type(type)
            .args(args)
            .build();

        return Rule.builder()
            .metadata(buildMetadata(name, "1"))
            .spec(spec)
            .build();
    }

    protected Rule buildSimpleLogRule(String name) {
        return buildRule(name, "1", Rule.RuleType.SIMPLE_LOG);
    }

    protected Rule buildRule(String name, Rule.RuleType type) {
        return buildRule(name, "1", type);
    }

    protected Rule buildRule(String name, String version, Rule.RuleType type) {
        Rule.Spec spec = Rule.Spec.builder()
            .type(type)
            .build();

        return Rule.builder()
            .metadata(buildMetadata(name, version))
            .spec(spec)
            .build();
    }

    /**
     * Relation
     */
    protected Relation buildRelation(Rule upper, Rule down) {
        Relation.Spec spec = Relation.Spec.builder()
            .down(down)
            .upper(upper)
            .build();

        String name = "relation-" + upper.getMetadata().getName() + "-" + down.getMetadata().getName();
        return Relation.builder()
            .metadata(buildMetadata(name, "1"))
            .spec(spec)
            .build();
    }

    /**
     * Message
     */
    protected Message buildMessage(int qos) {
        return Message.builder()
            .metadata(buildMetadata("message", "1"))
            .qos(qos)
            .message(DEMO_MESSAGE_CONTENT.getBytes(StandardCharsets.UTF_8))
            .build();
    }

    protected Metadata buildMetadata(String name, String version) {
        return Metadata.builder()
            .uid(UUID.randomUUID().toString())
            .name(name)
            .resourceVersion(version)
            .createTime(Instant.now().toEpochMilli())
            .updateTime(Instant.now().toEpochMilli())
            .build();
    }
}
